﻿/*
	Copyright 2010 Intuitive Solutions

	Licensed under the Apache License, Version 2.0 (the "License");
	you may not use this file except in compliance with the License.
	You may obtain a copy of the License at

	   http://www.apache.org/licenses/LICENSE-2.0

	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
*/

using System;
using System.Threading;
using System.Collections.Generic;

namespace Metamorph.Core.Bpm.Events
{
	public class EventManager
	{
		private static EventManager emInstance;

		private Dictionary<string, Timer>					dTimers;
		private Dictionary<string, EventDispatcher>			dDispatchers;
		private Dictionary<IBusinessProcess, EventContext>	dErrorDispatchers;

		private EventManager()
		{
			dTimers				= new Dictionary<string, Timer>();
			dDispatchers		= new Dictionary<string, EventDispatcher>();
			dErrorDispatchers	= new Dictionary<IBusinessProcess, EventContext>();
		}

		public static EventManager Instance
		{
			get
			{
				if (emInstance == null)
				{
					emInstance = new EventManager();
				}

				return emInstance;
			}
		}

		public void Subscribe(TimerEvent teTimerEvent, IBusinessProcess bpBusinessProcess)
		{
			string sTimerId = bpBusinessProcess.Id + teTimerEvent.Name;

			if (!dTimers.ContainsKey(sTimerId))
			{
				EventContext ecEventContext = new EventContext();

				ecEventContext.Event = teTimerEvent;
				ecEventContext.BusinessProcess = bpBusinessProcess;
				ecEventContext.ExecutionContext = bpBusinessProcess.ExecutionStack.Peek();

				Timer tTimer = new Timer(new System.Threading.TimerCallback(SchedulerThread), ecEventContext, teTimerEvent.TimerCycle, TimeSpan.FromMilliseconds(-1));

				dTimers.Add(bpBusinessProcess.Id + teTimerEvent.Name, tTimer);
			}
		}

		public void Subscribe(ErrorEvent eeErrorEvent, IBusinessProcess bpBusinessProcess)
		{
			EventContext ecEventContext = new EventContext();

			ecEventContext.Event = eeErrorEvent;
			ecEventContext.BusinessProcess = bpBusinessProcess;
			ecEventContext.ExecutionContext = bpBusinessProcess.ExecutionStack.Peek();

			dErrorDispatchers.Add(bpBusinessProcess, ecEventContext);
		}

		public void Subscribe(RuleEvent reRuleEvent, IBusinessProcess bpBusinessProcess)
		{
			EventListenerAttribute elaEventListenerAttribute = ((EventListenerAttribute[])((ConditionDispatcher)MetamorphManager.Instance.ConditionDispatcher).Conditions[reRuleEvent.Rule].Method.GetCustomAttributes(typeof(EventListenerAttribute), true))[0];

			Subscribe(reRuleEvent, elaEventListenerAttribute.Token, bpBusinessProcess);
		}

		public void Subscribe(IEvent eEvent, string sToken, IBusinessProcess bpBusinessProcess)
		{
			if (!dDispatchers.ContainsKey(sToken))
			{
				dDispatchers.Add(sToken, new EventDispatcher());
			}
			dDispatchers[sToken].AddListener(bpBusinessProcess, eEvent, bpBusinessProcess.ExecutionStack.Peek());
		}

		public void Unsubscribe(IEvent eEvent, string sToken, IBusinessProcess bpBusinessProcess)
		{
			if (dDispatchers.ContainsKey(sToken))
			{
				dDispatchers[sToken].RemoveListener(bpBusinessProcess, eEvent);

				if (dDispatchers[sToken].IsEmpty)
				{
					dDispatchers.Remove(sToken);
				}
			}
		}

		public void Subscribe(MessageEvent meMessageEvent, IBusinessProcess bpBusinessProcess)
		{
			Subscribe(meMessageEvent, meMessageEvent.Message, bpBusinessProcess);
		}

		public void Unsubscribe(RuleEvent reRuleEvent, IBusinessProcess bpBusinessProcess)
		{
			EventListenerAttribute elaEventListenerAttribute = ((EventListenerAttribute[])((ConditionDispatcher)MetamorphManager.Instance.ConditionDispatcher).Conditions[reRuleEvent.Rule].Method.GetCustomAttributes(typeof(EventListenerAttribute), true))[0];

			Unsubscribe(reRuleEvent, elaEventListenerAttribute.Token, bpBusinessProcess);
		}

		public void Unsubscribe(MessageEvent meMessageEvent, IBusinessProcess bpBusinessProcess)
		{
			Unsubscribe(meMessageEvent, meMessageEvent.Message, bpBusinessProcess);
		}

		public void Unsubscribe(TimerEvent teTimerEvent, IBusinessProcess bpBusinessProcess)
		{
			string sTimerId = bpBusinessProcess.Id + teTimerEvent.Name;

			if (dTimers.ContainsKey(sTimerId))
			{
				dTimers[sTimerId].Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
				dTimers[sTimerId].Dispose();

				dTimers.Remove(sTimerId);
			}
		}

		public void Subscribe(List<IEvent> lEvents, IBusinessProcess bpBusinessProcess)
		{
			bpBusinessProcess.ExecutionStack.Peek().Events = lEvents;

			if (lEvents != null && lEvents.Count > 0)
			{
				int iCount = 0;
				IEvent eInitialEvent = null;

				foreach (IEvent eEvent in lEvents)
				{
					if (!EventType.End.Equals(eEvent.EventType))
					{
						eEvent.Init(bpBusinessProcess);

						if(EventType.Start.Equals(eEvent.EventType)){
							eInitialEvent = eEvent;
							iCount++;
						}						
					}
				}

				if(iCount == 1)
				{
					bpBusinessProcess.ExecutionStack.Peek().InitialShape = eInitialEvent;
				}
				else
				{
					bpBusinessProcess.Interrupt = true;
				}
			}
		}

		public void Unsubscribe(List<IEvent> lEvents, IBusinessProcess bpBusinessProcess)
		{
			if (lEvents != null && lEvents.Count > 0)
			{
				foreach (IEvent eEvent in lEvents)
				{
					if (!EventType.End.Equals(eEvent.EventType))
					{
						eEvent.Uninit(bpBusinessProcess);
					}
				}
			}
		}

		public void Notify()
		{
			foreach (EventDispatcher edEventDispatcher in dDispatchers.Values)
			{
				edEventDispatcher.Notify();
			}
		}

		public void Notify(string sToken)
		{
			if (dDispatchers.ContainsKey(sToken))
			{
				dDispatchers[sToken].Notify();
			}
		}

		public void Notify(string sToken, object oMessage)
		{
			if (dDispatchers.ContainsKey(sToken))
			{
				dDispatchers[sToken].Notify(oMessage);
			}
		}

		public void Notify(IBusinessProcess bpBusinessProcess, Exception eException)
		{
			if (dErrorDispatchers.ContainsKey(bpBusinessProcess))
			{
				bpBusinessProcess.CurrentExeption = eException;

				bpBusinessProcess.Next(dErrorDispatchers[bpBusinessProcess].Event, dErrorDispatchers[bpBusinessProcess].ExecutionContext);
			}
			else
			{
				throw eException;
			}
		}

		public void Unsubscribe(ErrorEvent errorEvent, IBusinessProcess bpBusinessProcess)
		{
			if (dErrorDispatchers.ContainsKey(bpBusinessProcess))
			{
				dErrorDispatchers.Remove(bpBusinessProcess);
			}
		}

		public void SchedulerThread(object oObject)
		{
			EventContext ecEventContext = ((EventContext)oObject);

			ecEventContext.BusinessProcess.Next(ecEventContext.Event, ecEventContext.ExecutionContext);
		}
	}
}